package org.hertsig.deckbox;

import lombok.Data;
import lombok.Delegate;
import lombok.ToString;
import lombok.experimental.Wither;

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;

import au.com.bytecode.opencsv.CSVWriter;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultiset;

@ToString
public class Collection implements Iterable<Collection.Card> {
	private static final String[] INVENTORY_HEADERS = new String[] {"Count", "Tradelist Count", "Name", "Foil", "Textless", "Promo", "Signed", "Edition", "Condition", "Language"};
	private static final String[] TRADE_AND_WISHLIST_HEADERS = new String[] {"Count", "Name", "Foil", "Textless", "Promo", "Signed", "Edition", "Condition", "Language"};
	
	@Delegate(excludes=Iterable.class) 
	private final Multiset<Collection.Card> cards = TreeMultiset.create();
	
	public int count(String cardName) {
		return get(cardName).size();
	}
	
	public Multiset<Card> get(String name) {
		return Multisets.filter(cards, (card) -> Objects.equal(card.getName(), name));
	}
	
	@Override // apparently @Delegate won't add @Override, so manually declare it
	public Iterator<Card> iterator() {
		return cards.iterator();
	}
	
	/**
	 * Writes this collection as a CSV that can be imported to your inventory by the Deckbox CSV import feature
	 */
	public void writeInventoryCsv(Writer out) throws IOException {
		try (CSVWriter writer = new CSVWriter(out)) {
			writer.writeNext(INVENTORY_HEADERS);
			
			for (Collection.Card card : cards.elementSet()) {
				writer.writeNext(new String[]{
					String.valueOf(cards.count(card)),
					null,
					card.getName(),
					card.isFoil() ? "foil" : null,
					card.isTextless() ? "textless" : null,
					card.isPromo() ? "promo" : null,
					card.isSigned() ? "signed" : null,
					Strings.isNullOrEmpty(card.getEdition()) ? null : card.getEdition().replace("Planechase 2012 Edition", "Planechase 2012").replace("Magic 2014", "Magic 2014 Core Set"),
					Strings.isNullOrEmpty(card.getCondition()) ? null : card.getCondition(),
					Strings.isNullOrEmpty(card.getLanguage()) ? null : card.getLanguage()
				});
			}
		}
	}
	
	/**
	 * Writes this collection as a CSV that can be imported to your trade- or wishlist by the Deckbox CSV import feature
	 */
	public void writeListCsv(Writer out) throws IOException {
		try (CSVWriter writer = new CSVWriter(out)) {
			writer.writeNext(TRADE_AND_WISHLIST_HEADERS);
			
			for (Collection.Card card : cards.elementSet()) {
				writer.writeNext(new String[]{
						String.valueOf(cards.count(card)),
						card.getName(),
						card.isFoil() ? "foil" : null,
						card.isTextless() ? "textless" : null,
						card.isPromo() ? "promo" : null,
						card.isSigned() ? "signed" : null,
						Strings.isNullOrEmpty(card.getEdition()) ? null : card.getEdition().replace("Planechase 2012 Edition", "Planechase 2012"),
						Strings.isNullOrEmpty(card.getCondition()) ? null : card.getCondition(),
						Strings.isNullOrEmpty(card.getLanguage()) ? null : card.getLanguage()
				});
			}
		}
	}
	
	@Data @Wither @ToString(of={"name", "edition"})
	public static final class Card implements Comparable<Card> {
		private final String name;
		private final boolean foil;
		private final boolean textless;
		private final boolean promo;
		private final boolean signed;
		private final String edition;
		private final String condition;
		private final String language;
		
		@Override
		public int compareTo(Card other) {
			return ComparisonChain.start()
					.compare(getName(), other.getName())
					.compare(getEdition(), other.getEdition(), allowNull())
					.compare(getLanguage(), other.getLanguage(), allowNull())
					.compareFalseFirst(isFoil(), other.isFoil())
					.compareFalseFirst(isTextless(), other.isTextless())
					.compareFalseFirst(isPromo(), other.isPromo())
					.compareFalseFirst(isSigned(), other.isSigned())
					.compare(getCondition(), other.getCondition(), allowNull())
					.result();
		}
		
		private <T extends Comparable<T>> Ordering<T> allowNull() {
			return Ordering.natural().nullsFirst();
		}
	}
}
